package org.nanotek.dao;

import java.io.Serializable;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.criterion.Projections;
import org.nanotek.util.Base;
import org.nanotek.util.logic.Assert;
import org.springframework.dao.DataAccessException;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

public class TenantDAO<D extends DAO<Base<?>>, E extends Base<?>> extends BaseDAO implements DAOBag<DAO<Base<?>>, Base<?>> {


	/**
		Deixa ae que agora que foi criado, soh eh removido quando morre.
	 **/
	protected DAO<E> dAOInstance;

	public TenantDAO() {
	}

	@Transactional (readOnly = false, propagation = Propagation.REQUIRED)
	public Base<?> findById (Serializable id)
	{
		Session session = getSessionFactory().getCurrentSession();
		return (Base<?>) session.get(clazz, id);
	}

	@Override
	@Transactional (readOnly = true, propagation = Propagation.REQUIRED)
	public List<Base<?>> listRecords(Integer firstResult, Integer maxResults) {
		try {
			Session session = getSessionFactory().getCurrentSession();
			Criteria criteria = session.createCriteria(clazz);
			return criteria.setFirstResult(firstResult).setMaxResults(maxResults).list();
		} catch (DataAccessException e) {
			throw new DAOException(e);
		}
	}


	@Override
	@Transactional (readOnly = false, propagation = Propagation.REQUIRED)
	public Long getCount(Base<?> e) {
		Session session = getSessionFactory().getCurrentSession();
		Long countElements = (Long) session.createQuery("select count(o) from " + e.getClass().getName() + " o").uniqueResult();
		return countElements;
	}


	@Override
	@Transactional (readOnly = false, propagation = Propagation.REQUIRED)
	public boolean remove(Base<?> e) {
		Session session = getSessionFactory().getCurrentSession();
		session.delete(e);
		return false;
	}



	/***
	 * Need to verify why the collection method remains an object.
	 * since at this side is considered just objects known by the system internal.
	 * Objects of Class<Object> are considered, as from the outside world.
	 * wans't transformed to conform the system behavior.
	 */
	@Override
	@Transactional (readOnly = false, propagation = Propagation.REQUIRED)
	public boolean remove(Object o) {
		Assert.isInstanceOf(Base.class , o);
		remove(o);
		return false;
	}

	@Transactional (readOnly = false, propagation = Propagation.REQUIRED)
	@SuppressWarnings("unchecked")
	@Override
	public boolean remove(Base<?> e, int nCopies) {

		boolean queryResult = false;
		Session session = getSessionFactory().getCurrentSession();
	    Criteria criteria = session.createCriteria(clazz);
	    List<? extends Base<?>> results = criteria.setFirstResult(0).setMaxResults(nCopies).list();
	    queryResult = (results.size()>0)?true:false;
	    if ((results.size()>0))
		    for ( Base<?> result : results)
		    {
		    	session.delete(result);
		    }
		return queryResult;
	}

	@Transactional (readOnly = false, propagation = Propagation.REQUIRED)
	@Override
	public boolean add(Base<?> object) {
		getSessionFactory().getCurrentSession().persist(object);
		return true;
	}

	@Transactional (readOnly = false, propagation = Propagation.REQUIRED)
	@Override
	public boolean add(Base<?> object, int nCopies) {
		//Implement the test of "non-unique-means-shallow copy of the object
		// to assure that "new copies elements where created.
		for (int i = 0;i<nCopies;i++){
			getSessionFactory().getCurrentSession().persist(object);
		}
		return false;
	}


	@Transactional (readOnly = false, propagation = Propagation.REQUIRED)
	@SuppressWarnings("unchecked")
	@Override
	public Set<Base<?>> uniqueSet() {
		Set <Base<?>>set = new HashSet<Base<?>>();
		set.addAll((Collection<? extends Base<?>>) getUniqueElementCountBase(getClazz()));
		return set;
	}

	@Transactional (readOnly = true, propagation = Propagation.REQUIRED)
	private List<?> getUniqueElementCountBase(Class<Base<?>> clazz) {
		return getSessionFactory().
		getCurrentSession().
		createCriteria(clazz).
		setProjection(Projections.projectionList()
		.add(Projections.count("id"))).list();
	}

	@Transactional (readOnly = true, propagation = Propagation.REQUIRED)
	private List<?> getElementCountBase(Class<Base<?>> clazz) {
		return getSessionFactory().
		getCurrentSession().
		createCriteria(clazz)
		.list();
	}


	@Override
	public int size() {
		return getElementCountBase(getClazz()).size();
	}

	@Transactional (readOnly = true, propagation = Propagation.REQUIRED)
	@Override
	public boolean containsAll(Collection<?> coll) {
		List<?> result = getSessionFactory().
		getCurrentSession().
		createSQLQuery("from " + getClazz()  + " b where b in :l" )
		.setParameterList("l", coll).list();
		return (result.size()>0 && coll.size() == result.size())?true:false;
	}

	@Transactional (readOnly = false, propagation = Propagation.REQUIRED)
	@Override
	public boolean removeAll(Collection<?> coll) {
		//Identify if the condition is suficient.
		String deleteQuery = "Delete " + getClass() + " b where b in :l";

		return (getSessionFactory().
				getCurrentSession().
				createSQLQuery(deleteQuery).
				setParameterList("l", coll).executeUpdate()>0)?true:false;
	}

	@Transactional (readOnly = false, propagation = Propagation.REQUIRED)
	@Override
	public boolean retainAll(Collection<?> coll) {
		String deleteQuery = "Delete " + getClass() + " b where b not in :l";
		return (getSessionFactory().
		getCurrentSession().
		createSQLQuery(deleteQuery).
		setParameterList("l", coll).executeUpdate() >0)?true:false;
	}

	@Transactional (readOnly = true, propagation = Propagation.REQUIRED)
	@Override
	public Iterator<Base<?>> iterator() {
		return (Iterator<Base<?>>) getElementCountBase(getClazz()).iterator();
	}

	@Override
	public boolean isEmpty() {
		return (getElementCountBase(getClazz()).size()>0)?false:true;
	}

	@Transactional (readOnly = true, propagation = Propagation.REQUIRED)
	@Override
	public boolean contains(Object o) {
		String query = "from " + getClazz() + " where o in :o";
		return (getSessionFactory()
				.getCurrentSession()
				.createSQLQuery(query).setParameter("o", o).list().size()>0)?true:false;
	}

	@Override
	public Object[] toArray() {
		return getElementCountBase(getClazz()).toArray();
	}

	@SuppressWarnings("unchecked")
	@Override
	public <T> T[] toArray(T[] a) {
		List<T> result =  (List<T>) getElementCountBase(getClazz());
		return ((T[]) result.toArray());
	}

	@Transactional (readOnly = false, propagation = Propagation.REQUIRED)
	@Override
	public boolean addAll(Collection<? extends Base<?>> c) {
		Session session = getSessionFactory().getCurrentSession();
		for (Base<?> base : c) {
			session.persist(base);
		}
		return false;
	}

	@Transactional (readOnly = false, propagation = Propagation.REQUIRED)
	@Override
	public void clear() {
		String deleteQuery = "delete from " + getClazz();
		getSessionFactory().getCurrentSession().createSQLQuery(deleteQuery).executeUpdate();
	}


	@SuppressWarnings("unchecked")
	@Override
	public void setDAOInstance(DAO<Base<?>> daoInstance) {
		dAOInstance = (DAO<E>) daoInstance;
	}
}
